/*
 *    This file is part of SSHTunneler.
 *
 *    Copyright 2009 Jean-Sebastien Gelinas <calestar@gmail.com>
 *
 *    SSHTunneler is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    SSHTunneler is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with SSHTunneler.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package sshtunneler.packager.installergenerator;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import javax.swing.JOptionPane;

import sshtunneler.packager.data.AdditionalFiles;
import sshtunneler.packager.data.Config;
import sshtunneler.packager.data.PackagerServerContainer;
import sshtunneler.packager.data.PackagerServerTunnelFile;
import sshtunneler.packager.data.TunnelerConfig;

public class InstallerGenerator {

	public static void generate(File output) {
		try {
			ScriptGenerator script_generator = new ScriptGenerator();
			ConfigGenerator config_generator = new ConfigGenerator();
			String path_to_config = "";
			File tmp_dir = new File("tmp_build_dir/");
			File tun_dest = null;
			File output_zip = null;

			tmp_dir.mkdir();

			InstallerGenerator.extract_all_installer(tmp_dir);

			// Copy files from the SSHTunneler app.
			InstallerGenerator.copy_tunneler_files(tmp_dir);

			// Copy additionnal files
			InstallerGenerator.copy_additionnal_files(tmp_dir);

			// Write config and scripts
			path_to_config = tmp_dir.getAbsoluteFile() + File.separator
					+ "sshtunneler" + File.separator + "installer"
					+ File.separator;
			File configFile = new File(path_to_config + "Config.properties");
			File scriptFile = new File(path_to_config + "Script.properties");
			script_generator.generate_to(scriptFile);
			config_generator.generate_to(configFile);

			// Create Config if needed
			if (!TunnelerConfig.inst.isProg_update()) {
				tun_dest = new File(tmp_dir.getAbsoluteFile() + File.separator
						+ "SSHTunneler_Default.tun");
				PackagerServerTunnelFile.inst.save(tun_dest,
						PackagerServerContainer.inst.getServers());
			}

			// Repack everything !
			output_zip = new File("built_SSHTunnelerInstaller.jar");
			File manifest_file = new File(tmp_dir, "META-INF/MANIFEST.MF");
			FileInputStream man_in = new FileInputStream(manifest_file);
			Manifest man = new Manifest(man_in);
			JarOutputStream out = new JarOutputStream(new FileOutputStream(
					output_zip), man);
			out.setLevel(9);
			out.setMethod(ZipOutputStream.DEFLATED);
			String prefix_to_remove = tmp_dir.getAbsolutePath();
			InstallerGenerator.zipDir(prefix_to_remove, tmp_dir
					.getAbsolutePath(), out);
			out.close();
			man_in.close();

			// Copy to destination
			InstallerGenerator.copyFile(output_zip, output);

			JOptionPane.showMessageDialog(null, "Installer generated !");

			// Clean-up !
			if (output_zip != null) {
				output_zip.delete();
			}
			if (tun_dest != null) {
				tun_dest.delete();
			}
			InstallerGenerator.deleteDirectory(tmp_dir);
		} catch (ZipException ex) {
			Logger.getLogger(InstallerGenerator.class.getName()).log(
					Level.SEVERE, null, ex);
			JOptionPane.showMessageDialog(null, ex.getMessage());
		} catch (IOException ex) {
			Logger.getLogger(InstallerGenerator.class.getName()).log(
					Level.SEVERE, null, ex);
			JOptionPane.showMessageDialog(null, ex.getMessage());
		}
	}

	private static void zipDir(String prefix_to_remove, String dir2zip,
			JarOutputStream zos) throws FileNotFoundException, IOException {
		File zipDir = new File(dir2zip);
		String[] dirList = zipDir.list();
		byte[] readBuffer = new byte[2156];
		int bytesIn = 0;
		for (String element : dirList) {
			File f = new File(zipDir, element);
			if (f.isDirectory()) {
				String filePath = f.getPath();
				InstallerGenerator.zipDir(prefix_to_remove, filePath, zos);
				continue;
			}

			// Alright, skip 'META-INF/MANIFEST.MF'
			if (f.getPath().substring(prefix_to_remove.length() + 1).equals(
					"META-INF" + File.separator + "MANIFEST.MF")) {
				// Skip !
				continue;
			}

			/***************************************************************
			 * ZIP ENTRY FILES MUST HAVE '/' AS SEPARATOR ON ANY PLATFORM *
			 ***************************************************************/
			FileInputStream fis = new FileInputStream(f);
			JarEntry anEntry = new JarEntry(f.getPath().substring(
					prefix_to_remove.length() + 1).replace(File.separatorChar,
					'/'));
			zos.putNextEntry(anEntry);
			while ((bytesIn = fis.read(readBuffer)) != -1) {
				zos.write(readBuffer, 0, bytesIn);
			}
			fis.close();
			zos.flush();
			zos.closeEntry();
		}
	}

	public static void extract_all_installer(File dest) throws ZipException,
			IOException {
		String destination_str = dest.getAbsolutePath() + File.separator;
		byte[] buf = new byte[1024];
		ZipInputStream zipinputstream = null;
		ZipEntry zipentry;
		zipinputstream = new ZipInputStream(new FileInputStream(Config.inst
				.getDefaultInstallerFile()));

		zipentry = zipinputstream.getNextEntry();
		while (zipentry != null) {
			// for each entry to be extracted
			String entryName = zipentry.getName();
			int n;
			FileOutputStream fileoutputstream;

			if (zipentry.isDirectory()) {
				String full_path = destination_str + entryName;
				(new File(full_path)).mkdirs();
				zipentry = zipinputstream.getNextEntry();
				continue;
			}

			fileoutputstream = new FileOutputStream(destination_str + entryName);

			while ((n = zipinputstream.read(buf, 0, 1024)) > -1) {
				fileoutputstream.write(buf, 0, n);
			}

			fileoutputstream.close();
			zipinputstream.closeEntry();
			zipentry = zipinputstream.getNextEntry();
		}

		zipinputstream.close();
	}

	public static void copyFile(File in, File out) throws IOException {
		FileChannel inChannel = new FileInputStream(in).getChannel();
		FileChannel outChannel = new FileOutputStream(out).getChannel();
		try {
			inChannel.transferTo(0, inChannel.size(), outChannel);
		} catch (IOException e) {
			throw e;
		} finally {
			if (inChannel != null) {
				inChannel.close();
			}
			if (outChannel != null) {
				outChannel.close();
			}
		}
	}

	private static void copy_tunneler_files(File tmp_dir) throws IOException {
		File jar = Config.inst.getDefaultTunnelerFile();

		File dest = new File(tmp_dir.getAbsoluteFile() + File.separator
				+ jar.getName());
		InstallerGenerator.copyFile(jar, dest);
	}

	private static void copy_additionnal_files(File tmp_dir) throws IOException {
		File dest_dir = new File(tmp_dir.getAbsoluteFile() + File.separator);

		File dest = null;

		for (File f : AdditionalFiles.inst.files()) {
			String file_name = f.getName();
			dest = new File(dest_dir.getAbsolutePath() + File.separator
					+ file_name);
			InstallerGenerator.copyFile(f, dest);
		}
	}

	static private boolean deleteDirectory(File path) {
		if (path.exists()) {
			File[] files = path.listFiles();
			for (File file : files) {
				if (file.isDirectory()) {
					InstallerGenerator.deleteDirectory(file);
				} else {
					file.delete();
				}
			}
		}
		return (path.delete());
	}
}
